Net.h++ is an object-oriented, C++ class library that makes it easier to write applications that communicate across a network. It provides a clean, maintainable way to write code for client/server and peer to peer communications. The sophisticated, object-oriented design allows developers to write code which is both protocol independent and cross platform.
Net.h++ has a layered, modular design. It gives you the ability to write code to a specific protocol (such as TCP/IP), or to a generic Portal Layer that interfaces with a variety of protocols. The Communication Adapter Layer, Portal Layer, and Communication Services Layer work together to provide a flexible, open architecture. Each layer builds on the Communication Adapter Layer providing increasing levels of abstraction. A developer can use any or all of the layers of Net.h++ depending on the requirements of the application.
The initial release of Net.h++ includes a C++ implementation of the Berkeley sockets API _ the defacto standard TCP/IP interface. The Net.h++ Socket Adapter uses the same concepts found in the underlying 'C' API, including sockets, socket addresses, and Internet hosts, but with an intuitive, object-oriented interface. It leverages the most powerful features of C++, including exception handling and templates, without sacrificing any of the functionality of Berkeley sockets. A developer can use the C++ Socket Adapter classes directly or can program to the portable layer that handles communication channel specifics below the interface level. Net.h++ is available in source and object code forms for Unix, Windows NT, and Windows 3.1
This section will first define some necessary terms, then describe in some detail the various layers of the Net.h++ architecture.
A communication channel is a path of communication between processes. The channel itself is established, maintained, and dismantled by the operating system kernel. API's such as sockets, named pipes, and TLI allow a user level program to access a channel.
We will refer to channels created with different C APIs as different types of channels. For example, a socket channel type is different than a TLI channel type, despite the fact that the kernel creates the same internal data structures for each.
A portal is an abstraction of an access point to a communications channel. It is maintained by the user level program. In most cases, the operating system returns a portal to a user level program right after creating a channel. The user program can then use the portal to make more portals to the same channel, to send or receive data from the channel, or to control the channel. In some systems (system V streams, e.g.) a portal can be passed from one running program to another. Examples of portals in the C communication APIs are UNIX file descriptors and Windows Winsock handles
Net.h++ consists of three architectural layers: the Communication Adapter layer, the Portal layer, and the Communication Services layer. Each layer is built on the one before and offers increasingly abstract services. All three layers are built on a foundation of fundamental datatypes. You can use any or all of the layers of Net.h++, plugging in at the points which satisfy your particular requirements.
Here's a three-D figure showing the architecture:
As you can see, there are four parts to the Net.h++ architecture: the foundation and the three Net.h++ layers. Let's describe each part.
Compilers shipping today generally lack the string and container classes that are needed to build a sophisticated product like Net.h++. The Net.h++ Foundation provides these types, along with other services needed by all the layers. As well as simplifying the implementation of Net.h++, the Foundation makes the interface much easier to use and understand.
Most of the Foundation is not actually part of Net.h++ itself: it's Tools.h++, the most popular C++ foundation library available.
Each adapter in the Communication Adapters layer is a complete, object oriented, C++ programming interface to a communications system, or a part of a communications system. The communication adapters are designed for maximum power and efficiency. They give you complete control over the communications channel.
Often, a communication adapter encapsulates an existing procedural API, such as BSD sockets or the NetWare C interface. In these cases, the corresponding Net.h++ adapter uses the same concepts and terminology as the procedural API, making the adapter especially easy to use if you have experience with the procedural API. All of the functionality of the procedural interface is preserved. The adapter will be designed to work together with legacy code written using the C API, by providing the ability to "kickdown" from C++ objects to the native data structures.
Communication adapters use object oriented concepts and C++ language features to make the underlying system easier to use. For example:
Tools.h++ classes are used to simplify the interface.
overloaded functions and default arguments are used to make the most common cases easy to program.
exceptions are used to provide a flexible, easy to use, error handling system.
The Portal Layer provides a communications interface which can use any communication adapter for its underlying communication channel. This portable interface is used by the Communication Services Layer to provide transport independent services.
The third layer in Net.h++ consists of modules which leverage the Portal Layer to provide transport independent communication services. The services provide functionality at a high level of abstraction. Please see section 3.4 for information on the IOStream module provided in Net.h++ 1.0.
While the overall architecture of Net.h++ has been designed to accommodate many different network protocols and communication services, Net.h++ version 1.0 is an encapsulation of Berkeley sockets using the TCP and UDP protocols. The Net.h++ sockets implementation includes a Socket Adapter which sits in the Communication Adapter Layer described above. Also included is an IOStream Module which plugs into the Communication Services Layer.
Design patterns use formal notations to denote relationships and interactions between classes and objects. The Net.h++ class diagrams on the following pages depict the static relationships between classes. In the diagram, classes are denoted by a rectangular box with the class name inside. These relationships between classes are defined by the following conventions.
Here is a diagram of the modules that are available in the first release of Net.h++. The diagrams that show up faintly inside the module boxes are the class relationships within and between the modules.
The Net.h++ Version 1.0 Communication Adapter Layer consists of a Socket Adapter which models the concepts of sockets directly. The concepts and corresponding classes are as follows:
socket descriptors, ints under UNIX and SOCKET under Winsock, are modeled using objects of type RW Socket.
In the Berkeley sockets 'C' API, general socket addresses are modeled using simulated polymorphism _ the "base type" is represented using a structure cast to a struct sockaddr. In Net.h++, a truly polymorphic interface is used _ the base class, corresponding to the sockaddr structure, is RWSockAddr.
Internet domain addresses are modeled using the RWInetAddr class. This corresponds to the sockaddr_in structure in the C API.
Internet hosts names and addresses are represented using the RWInetHost class. This corresponds to the hostent structure in the C API. It is also used where a network address IP integer would be used.
Internet port names and service names are represented in C by the servent structure. The corresponding C++ class is RWInetPort.
The relationships between the classes are shown in the following diagram.
If you try and read or write to a socket whose buffers are full, the socket will normally block (pause your program until it can complete the operation). This is a problem if we are trying to work with more than one socket at a time. For example, consider a program with two open sockets, s1 and s2, which prints to the screen any input that arrives on a socket. No data is currently available on either socket. If we read from s1, the program will block until data is available on that socket. In the meantime, data could arrive on s2, but we won't get to it. Well, lucky for us, we can.
What we'd really like to do is block on both sockets simultaneously, waiting until data could be read on either s1 or s2. Using Net.h++, you can do exactly that. First, you create a list of socket conditions that you are interested in - that data can be read on s1 or that data can be read on s2. These conditions are represented by instances of the RWSocketAttribute class, the list is a standard Tools.h++ data structure. You can then wait until one of the conditions is true, and handle each condition in turn.
The Net.h++ version 1.0 Portal Layer includes the RWPortal class, and implementation for socket channels, and an RWSocketPortal class.
The Portal Layer provides a channel independent portal class, RWPortal, which supports all of the operations performed on portals, except for creation of the channel and creation of the portal. The RWPortal class is a lightweight concrete class. You can use its copy constructor or assignment operator to create new portals to the same communication channel. There are no virtual functions in the RWPortal interface - this allows you to copy a reference to a portal to another portal without changing the semantics of how the portal will behave. This is useful in using a portal as a member of a class, for example.
RWPortal is implemented using the interface-implementation design pattern also used in Rogue Wave's DBTools.h++ class library. Each RWPortal has associated with it an implementation _ an object of a class derived from RWPortalImp. The RWPortalImp object is reference counted and lives on the heap. The RWPortal object is lightweight because the only state it contains is a pointer to its implementation. When a new portal is created from an existing portal using the copy constructor or the assignment operator, the new portal simply references the existing portal's implementation. Polymorphism happens in the implementation object, not in the RWPortal itself; this is why RW Portal does not need any virtual functions. This leads to an efficient design: for a simple send or receive, the only place where a virtual function is called is in the implementation object. All other functions calls are non-virtual, and can even be inlined.
Communications channels are usually used in three stages.
The channel and a portal are established, and options may be set on the channel. This is the connection phase.
The portal is used to send and receive information to and from the channel.
The portal is closed down. If no more portals into the channel are open, then the channel itself is closed down.
The RWPortal class is used in the 2nd and 3rd phases. The member functions of RW Portal allow sending and receiving of information, and the destructor is responsible for closing down the communication channel if necessary. A generic portal cannot be used for connection establishment, because the semantics of this process vary wildly from one type of channel to another.
The first phase, connection establishment, is handled by classes derived from RWPortal. These classes work closely with their corresponding classes in the Communication Adapter Layer to provide all the functionality necessary for establishing a connection. Once the connection is established, you may treat the resulting object as an RW Portal. This "slicing" of the portal is acceptable since the part you are slicing off is only needed for establishing a connection.
A design goal of the RWPortal class is to allow your application to polymorphically use different connection types without having to templatize all of the application code, and all of the communication services, on the specific type of communication channel. This goal can be achieved because generally the only interface which varies a lot between channel types is for the connection phase.
The IOStreams communication service module lets you program portal input/output using the C++ IOStreams classes. This allows you to use stream insertion and extraction operators to send objects through a communications channel. Using the Rogue Wave virtual streams available in Tools.h++, you can even send complicated data structures through the communication channel and preserve their morphology.
Since the portal class has abstracted the type of channel, we need only one set of iostream classes to attach iostreams to an RWPortal object. The key class in this layer is the RWPortalStreambuf class. RWPortalStreambuf is derived from streambuf and uses the portal as the source and sink for its buffer.
In addition to RWPortalStreambuf, the iostream classes RW PortalOStream and RWPortalIStream are provided. The constructors for these streams require an RWPortal object.
Since RWPortal abstracts away the socket specifics, the prototype IOStream Module does not make use of any socket concepts at all. Instead, the IOStream classes use only the RWPortal class.
As stated earlier, Net.h++ significantly simplifies network programming. The following examples illustrate this point.
This example shows client side code for interaction with a "Greeting" server that will return the greeting that was sent to it from the previous connection. To be fair, we simplified the Berkeley socket's code greatly by using Rogue Wave's RW CString from Tools.h++ (Since everyone already has a copy -- right?). Without the use of RW CString, the Berkeley sockets example would be even uglier!
================ Net.h++ client code ================
#include <iostream.h>
#include <rw/net/sockport.h>
#include <rw/net/inetaddr.h>
main(int argc, char **argv)
{
RWCString greeting;
cout << "Type in a greeting: " << flush;
greeting.readLine(cin);
try {
RWSocketPortal port( RWInetAddr(3010,"cold.roguewave.com") );
port.sendAtLeast( greeting );
RWCString incoming;
while ( !(incoming=sock.recv()).isNull() ) {
cout << incoming << flush;
}
}
catch (const RWxmsg& x) {
cerr << "Error: " << x.why() << endl;
}
return 0;
}
================ BSD-sockets client code ================
#include <iostream.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h> /* looking for read */
#include <string.h> /* looking for strlen */
#include <sys/types.h> /* network includes */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> /* gethostbyname() */
#include <rw/cstring.h>
const int AL_BUFSIZE = 512; // a small buffer, so it'll fit on the stack
int main(int argc, char **argv)
{
RWCString greeting;
cout << "Type in a greeting: " << flush;
greeting.readLine(cin);
// Set up address structure for host connection
struct hostent *h = gethostbyname("cold.roguewave.com");
if (h==0) {
cerr << "Error: Can't find host\n";
exit(1);
}
struct in_addr hostaddr = *(struct in_addr*)*h->h_addr_list;
struct sockaddr_in addr;
memset((void*)&addr, 0, sizeof(sockaddr)); // unused bytes must be zero
addr.sin_family = AF_INET;
addr.sin_port = htons(3010);
addr.sin_addr = *(struct in_addr*)*h->h_addr_list;
// Connect socket to address
int sock = socket(PF_INET,SOCK_STREAM,0);
if (sock<0) {
cerr << "Error: failed to create socket\n";
exit(1);
}
if ( connect(sock,(sockaddr*)&addr,sizeof(addr)) != 0 ) {
cerr << "Error: failed to connect - error " << errno << endl;
exit(1);
}
// Send message to the socket
while (!greeting.isNull()) {
int n = write(sock,greeting.data(),greeting.length());
if (n>0) {
greeting.remove(0,n);
} else {
cerr << "Failure writing to connection" << endl;
exit(1);
}
}
// Hear what the socket has to say
char buf[AL_BUFSIZE+1];
int n;
while((n=read(sock,&buf,AL_BUFSIZE))>0) {
buf[n] = '\0';
cout << buf << flush;
}
return 0;
}
================ Net.h++ server code ================
#include <rw/net/sockport.h>
#include <rw/net/portstrm.h>
#include <rw/net/inetaddr.h>
static RWCString lastGreeting = "It's my first time. Honest";
main()
{
RWSocketListener listener( RWInetAddr(3010) );
for (;;) {
RWPortal portal = listener(); // get the next incoming connection
RWPortalIStream strm(portal);
portal.sendAtLeast(lastGreeting); // send previous greeting
lastGreeting.readLine(strm); // read this client's greeting
}
return 0;
}
Net.h++ is THE solution for writing network applications in C++. Table 1 shows a summary of benefits and associated features.
Classes that relate to the socket specific functionality of Net.h++.
RWSocket is a wrapper for the C concept of a socket. Its member functions correspond exactly with the C functions in the Berkeley sockets API. Typically, RWSocket member functions have the same names as the corresponding C API functions, but the arguments and return values may be different to reflect the C++ environment.
RWSockAddr is a proxy to a socket address of some unknown type. RWSockAddr keeps a handle to a reference counted object which is the real address.
RWInetAddr is a complete internet address: type information, a host, and a port.
RWInetHost encapsulates an internet host IP address and its names. You can construct an RW InetHost from either an IP address or a symbolic name.
RWInetPort encapsulates an internet port and its service names. You can construct an RW InetPort from either an explicit port number or a symbolic service name.
RWInetType is the Internet address type. This class provides a convenient mechanism to construct an RWSockType for an Internet address type.
RWSocketListener simplifies creating sockets which listen for incoming connections on a particular address.
An RWSocketAttribute represents a condition on a particular socket. RW SocketAttributes are used to wait for multiple socket events asynchronously.
Classes that relate to the Portal Layer of Net.h++.
An RWPortal is an access point of a reliable byte stream communication channel. It is possible for more than one portal to access the communications channel. Portals are implemented using the interface-implementation paradigm. The portal itself is really a handle to an implementation that represents the communication channel.
The RWSocketPortal class provides a socket implementation of Portal build on top of the RW Socket class.
Classes that relate to the IOStream Module of Net.h++.
An RWPortalStreambuf is an implementation of a iostream streambuf which uses a portal for its source and sink of bytes.
The RWPortalIStream provides an istream which uses the RWPortal as its source of bytes. The RWPortal can be attached to any of the communications channels supported by Net.h++.
The RWPortalOStream provides an ostream which uses an RWPortal as its sink of bytes. The RWPortal can be attached to any of the communications channels supported by Net.h++.